package com.vois.jack.btmgr.devices.WLBleDataDevice;

import android.bluetooth.BluetoothGatt;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

import androidx.annotation.NonNull;

import com.vois.jack.btmgr.blebase.BleAction;
import com.vois.jack.btmgr.blebase.BleConstant;
import com.vois.jack.btmgr.blebase.BleDevCommonMsg;
import com.vois.jack.btmgr.devices.WLCommonBleDev.DefaultWLBleDevice;
import com.vois.jack.btmgr.util.Logger;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

public class WLBleDataDevice extends DefaultWLBleDevice {
    private static final Logger logger = Logger.getLogger(WLBleDataDevice.class);
    protected static final UUID SOCKET_UUID = UUID.fromString("00004105-0000-1000-8000-00805F9B34FB");
    private SendHandler sendHandler;
    private final List<BlePacket> sendingPacketList;
    private BlePacket currentSendingPacket;
    private HandlerThread sendThread;
    private int dataMtu = 0;
    private static final int MAX_RESEND_TIMES = 6;
    private static final int MAX_MTU = 240;
    private static final int MAX_MTU_SMALLER = 128;
    private static final int MAX_MTU_SMALLEST = 23;
    private static final int MSG_SEND_PACKET = 502;
    private static final int MSG_KEEP_SEND_PACKET_ITEM = 322;
    private static final int MSG_SEND_DATA_RESULT = 7;
    private static final int MSG_RESEND_PACKET = 449;
    private static final int MSG_RECV_PACKET = 992;
    private static final int MSG_SEND_TIMEOUT = 961;
    private HandlerThread recvThread;
    private RecvHandler recvHandler;

    private Timer sendMonitor;

    static public class ConfigData {
        public String channel;
    }

    public static class BlePacket {
        public short totalCount;
        public long id;
        public List<BlePacketItem> items;
        long created;
    }
    public static class BlePacketItem {
        public long id;
        public short seq;
        public short len;
        public byte[] data;

        @Override
        public String toString() {
            return "BlePacket{" +
                    "id=" + id +
                    ", seq=" + seq +
                    ", len=" + len +
                    '}';
        }
    }

    static class SendHandler extends Handler {
        private final WeakReference<WLBleDataDevice> weakReference;
        private int retrySendCount;
        public SendHandler(@NonNull Looper looper, WLBleDataDevice device) {
            super(looper);
            weakReference = new WeakReference<>(device);
        }

        private void sendPacket(BlePacket blePacket) {
            byte[] data;
            WLBleDataDevice device = weakReference.get();
            logger.d("sendPacket: " + (device != null) + " " + blePacket.items.size());
            if (device != null) {
                // 如果包还有其他帧，则继续发送
                if (blePacket.items.size() > 0) {
                    BlePacketItem item = blePacket.items.get(0);
                    logger.d("sendPacket: " + item);
                    if (item != null) {
                        data = new byte[item.len + 12];
                        ByteBuffer buffer = ByteBuffer.wrap(data);
                        buffer.order(ByteOrder.LITTLE_ENDIAN);

                        buffer.putShort((short) (item.len + 10));
                        buffer.putLong(item.id);
                        buffer.putShort(item.seq);
                        buffer.put(item.data, 0, item.len);

                        if (device.sendMonitor != null) {
                            device.sendMonitor.cancel();
                            device.sendMonitor = null;
                        }

                        logger.d("sending data to ble: " + data.length);
                        device.writeCharacteristicData(
                                WLBleDataDevice.SERVICE_UUID,
                                WLBleDataDevice.SOCKET_UUID,
                                data, data.length, new BleAction.BleActionCallback() {
                                    @Override
                                    public void onActionResult(int status, Bundle result) {
                                        logger.d("SOCKET_UUID onActionResult: " + status);
                                        if (status != BluetoothGatt.GATT_SUCCESS) {
                                            Message message = obtainMessage();
                                            message.what = MSG_RESEND_PACKET;
                                            sendMessage(message);
                                        } else {
                                            device.sendMonitor = new Timer();
                                            device.sendMonitor.schedule(new TimerTask() {
                                                @Override
                                                public void run() {
                                                    Message message = obtainMessage();
                                                    message.what = MSG_SEND_TIMEOUT;
                                                    message.obj = Long.valueOf(blePacket.id);
                                                    sendMessage(message);
                                                }
                                            }, 5000);
                                        }
                                    }
                                });
                    }
                }else {
                    logger.d("sendPacket: send done because packet is empty");
                }
            }
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            WLBleDataDevice device = weakReference.get();
            switch (msg.what) {
                case MSG_SEND_PACKET: {
                    logger.d("MSG_SEND_PACKET: device is not null:" + (device != null) + " packetlist: " + (device.sendingPacketList != null));
                    if (device != null) {
                        if (device.currentSendingPacket == null) {
                            logger.d("ready to enter criterion");
                            synchronized (device.sendingPacketList) {
                                logger.d("size of packet list" + device.sendingPacketList.size());
                                if (device.sendingPacketList.size() > 0) {
                                    device.currentSendingPacket = device.sendingPacketList.remove(0);
                                    logger.d("Sending packet id: " + device.currentSendingPacket.id);
                                    retrySendCount = 0;
                                    sendPacket(device.currentSendingPacket);
                                }
                            }
                        }
                    }
                }
                break;

                case MSG_SEND_DATA_RESULT: {
                    Message message = obtainMessage();
                    // 发送完毕（至少发到蓝牙端是OK的)
                    if (msg.arg1 == 1) {
                        // 如果发送成功，则去除上次发送的数据
                        if (device.currentSendingPacket.items.size() > 0) {
                            device.currentSendingPacket.items.remove(0);
                        }
                        message.what = MSG_KEEP_SEND_PACKET_ITEM;
                    } else {
                        // 如果发送失败，重新发送
                        message.what = MSG_RESEND_PACKET;
                    }
                    sendMessageDelayed(message, msg.arg2);

                }
                break;

                case MSG_KEEP_SEND_PACKET_ITEM: {
                    logger.d("MSG_KEEP_SEND_PACKET_ITEM: device is not null:" + (device != null) + " packetlist item size: " + device.currentSendingPacket.items.size());
                    if (device != null) {
                        if (device.currentSendingPacket.items.size() > 0) {
                            retrySendCount = 0;
                            sendPacket(device.currentSendingPacket);
                        }else {
                            logger.d("MSG_KEEP_SEND_PACKET_ITEM: listener is not null:" + (device.getListener() != null));
                            logger.d("BLE_DEV_COMMON_DATA_SEND_DATA_ACK true " + device.currentSendingPacket.id);
                            if (device.getListener() != null) {
                                try {
                                    Message message = new Message();
                                    message.what = BleDevCommonMsg.BLE_DEV_COMMON_DATA_SEND_DATA_ACK.getValue();
                                    Bundle bundle = new Bundle();
                                    bundle.putBoolean(BleConstant.EXTRA_VALUE_RESULT, true);
                                    bundle.putLong(BleConstant.EXTRA_MSG_ID, device.currentSendingPacket.id);
                                    message.setData(bundle);
                                    device.getListener().onMessage(device.getBluetoothDevice(), message);
                                }catch (Exception e) {
                                    e.printStackTrace();
                                    logger.d("error: " + e.toString());
                                }
                            }

                            device.currentSendingPacket = null;
                            Message message = obtainMessage();
                            message.what = MSG_SEND_PACKET;
                            sendMessage(message);
                        }
                    }
                }
                break;

                case MSG_SEND_TIMEOUT: {
                    logger.d("MSG_SEND_TIMEOUT: device is not null:" + (device != null));
                    retrySendCount = 0;
                    if (device.currentSendingPacket != null) {
                        if (device.getListener() != null) {
                            Message message = new Message();
                            message.what = BleDevCommonMsg.BLE_DEV_COMMON_DATA_SEND_DATA_ACK.getValue();
                            Bundle bundle = new Bundle();
                            bundle.putBoolean(BleConstant.EXTRA_VALUE_RESULT, false);
                            bundle.putLong(BleConstant.EXTRA_MSG_ID, device.currentSendingPacket.id);
                            message.setData(bundle);
                            device.getListener().onMessage(device.getBluetoothDevice(), message);
                        }
                    }

                    device.currentSendingPacket = null;
                    Message message = obtainMessage();
                    message.what = MSG_SEND_PACKET;
                    sendMessage(message);
                }
                break;

                case MSG_RESEND_PACKET: {
                    logger.d("MSG_RESEND_PACKET: device is not null:" + (device != null) + " retrySendCount: " + retrySendCount);
                    if (device != null) {
                        if (device.currentSendingPacket != null) {
                            retrySendCount++;
                            if (retrySendCount < MAX_RESEND_TIMES) {
                                sendPacket(device.currentSendingPacket);
                            }else {
                                logger.d("BLE_DEV_COMMON_DATA_SEND_DATA_ACK false");
                                if (device.getListener() != null) {
                                    Message message = new Message();
                                    message.what = BleDevCommonMsg.BLE_DEV_COMMON_DATA_SEND_DATA_ACK.getValue();
                                    Bundle bundle = new Bundle();
                                    bundle.putBoolean(BleConstant.EXTRA_VALUE_RESULT, false);
                                    bundle.putLong(BleConstant.EXTRA_MSG_ID, device.currentSendingPacket.id);
                                    message.setData(bundle);
                                    device.getListener().onMessage(device.getBluetoothDevice(), message);
                                }

                                if (device.currentSendingPacket != null) {
                                    device.currentSendingPacket.items.clear();
                                    device.currentSendingPacket = null;
                                }

                                Message message = obtainMessage();
                                message.what = MSG_SEND_PACKET;
                                sendMessage(message);
                            }
                        }
                    }
                }
                break;
            }
        }
    }

    static class RecvHandler extends Handler {
        WeakReference<WLBleDataDevice> weakReference;
        private final Map<Long, BlePacket> recvDataMap;
        public RecvHandler(@NonNull Looper looper, WLBleDataDevice device) {
            super(looper);
            weakReference = new WeakReference<>(device);
            recvDataMap = new HashMap<>();
        }

        private void handleDataRecv(byte[] data) {
            ByteBuffer buffer = ByteBuffer.wrap(data);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            short dataLen = buffer.getShort();
            try {
                BlePacket blePacket;
                BlePacketItem item = new BlePacketItem();
                // 10 means id 8 + seq 2
                item.len = (short) (dataLen - 10);
                item.id = buffer.getLong();
                item.seq = buffer.getShort();
                if (recvDataMap.containsKey(item.id)) {
                    blePacket = recvDataMap.get(item.id);
                    logger.d("onRecvData: blePacket is not null " + (blePacket != null));
                    assert blePacket != null;
                } else {
                    blePacket = new BlePacket();
                    blePacket.items = new ArrayList<>();
                    blePacket.created = SystemClock.elapsedRealtime();
                }

                // 头部帧会包含2个字节存包总数
                if (item.seq == 0) {
                    blePacket.totalCount = buffer.getShort();
                    item.len -= 2;
                }

                item.data = new byte[item.len];
                buffer.get(item.data);
                blePacket.items.add(item);

                logger.d("onRecvData: " + item.toString());
                long now = SystemClock.elapsedRealtime();
                blePacket.created = now;
                recvDataMap.put(item.id, blePacket);

                Iterator<Long> iterator = recvDataMap.keySet().iterator();

                // 如果等待接收的包，时间超过60秒，则认为包丢失，要去除
                while (iterator.hasNext()) {
                    Long id = iterator.next();
                    BlePacket packet = recvDataMap.get(id);
                    if (now - packet.created > 60000) {
                        logger.d("onRecvData: remove " + id);
                        iterator.remove();
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
                logger.d("onRecvData: " + e.toString());
            }
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            WLBleDataDevice device = weakReference.get();
            logger.d("MSG_RECV_PACKET: device is not null:" + (device != null));
            if (msg.what == MSG_RECV_PACKET) {
                if (device != null) {
                    handleDataRecv((byte[]) msg.obj);

                    Iterator<Long> iterator = recvDataMap.keySet().iterator();
                    while (iterator.hasNext()) {
                        Long id = iterator.next();
                        BlePacket blePacket = recvDataMap.get(id);
                        logger.d("blePacket is not null " + (blePacket != null));
                        assert blePacket != null;

                        logger.d("totalCount:" + blePacket.totalCount + " items size:" + blePacket.items.size());

                        // 如果包数等于总数，那么认为接收完毕，往外发送消息BLE_DEV_COMMON_DATA_RECV_IND
                        if (blePacket.totalCount == blePacket.items.size()) {
                            int totalLen = 0;
                            for (BlePacketItem i : blePacket.items) {
                                totalLen += i.len;
                            }
                            byte[] totalData = new byte[totalLen];
                            int offset = 0;
                            for (BlePacketItem i : blePacket.items) {
                                System.arraycopy(i.data, 0, totalData, offset, i.len);
                                offset += i.len;
                            }

                            // 此时已经接收完毕，可以清除缓存的包
                            iterator.remove();

                            logger.d("listener is not null " + (device.getListener() != null));
                            if (device.getListener() != null) {
                                Message message = new Message();
                                message.what = BleDevCommonMsg.BLE_DEV_COMMON_DATA_RECV_IND.getValue();
                                Bundle bundle = new Bundle();
                                bundle.putByteArray(BleConstant.EXTRA_DATA_TYPE, totalData);
                                message.setData(bundle);
                                device.getListener().onMessage(device.getBluetoothDevice(), message);
                                logger.d("BLE_DEV_COMMON_DATA_RECV_IND data len:" + totalData.length);
                            }
                        }
                    }
                }
            }
        }
    }

    public long sendData(byte[] data) {
        // 包包含长度，id，序号，这里占用12字节(id占用8字节)
        int dataSlotSize = dataMtu - 12;

        // 计算包数据的大小
        int packetCount = (data.length + 2) / dataSlotSize;
        int left = data.length + 2;
        long id = SystemClock.elapsedRealtime();
        int totalSize = 0;
        BlePacket blePacket = new BlePacket();
        blePacket.id = id;
        blePacket.totalCount = (short) packetCount;
        blePacket.items = new ArrayList<>();

        logger.d("sending data length:" + data.length + " dataSlotSize:" + dataSlotSize);

        if ((data.length + 2) % dataSlotSize != 0) {
            packetCount++;
        }

        logger.d("sendData: " + packetCount);

        for (int i = 0; i < packetCount; i++) {
            int dataOffset = 0;
            BlePacketItem item = new BlePacketItem();
            item.id = id;
            logger.d("left len: " + (data.length - totalSize));
            if (left < dataSlotSize) {
                item.len = (short) left;
            }else {
                item.len = (short) dataSlotSize;
            }
            item.seq = (short) i;
            item.data = new byte[item.len];

            if (i == 0) {
                ByteBuffer buffer = ByteBuffer.wrap(item.data);
                buffer.order(ByteOrder.LITTLE_ENDIAN);
                buffer.putShort((short) packetCount);
                dataOffset = 2;
            }

            logger.d("data offset:" + totalSize);
            System.arraycopy(data, totalSize, item.data, dataOffset, item.len - dataOffset);
            totalSize += item.len;
            left -= item.len;
            if (i == 0) {
                totalSize -= 2;
            }

            blePacket.items.add(item);
            logger.d("sendData: " + item.toString());
        }

        synchronized (sendingPacketList) {
            sendingPacketList.add(blePacket);
        }

        Message msg = new Message();
        msg.what = MSG_SEND_PACKET;
        sendHandler.sendMessage(msg);

        logger.d("Send Total Size:" + totalSize);

        return id;
    }

    private void onRecvData(byte[] data) {
        ByteBuffer buffer = ByteBuffer.wrap(data);
        logger.d("onRecvData: " + Arrays.toString(data));
        buffer.order(ByteOrder.LITTLE_ENDIAN);

        short dataLen = buffer.getShort();
        if ((dataLen & 0xFFFF) == 0xEEFF) {
            int len = 0;
            int flag = 0;
            int delay = 0;

            if (sendMonitor != null) {
                sendMonitor.cancel();
                sendMonitor = null;
            }

            try {
                len = buffer.getShort();
                flag = buffer.get();
                delay = 0;

                if (len == 5) {
                    delay = buffer.getInt();
                }

                logger.d("onRecvData: ACK " + len + " " + flag + " " + delay);
            }catch (Exception e) {
                e.printStackTrace();
                logger.d("onRecvData: " + e.toString());
                return;
            }

            Message message = sendHandler.obtainMessage();
            message.what = MSG_SEND_DATA_RESULT;
            message.arg1 = flag;
            message.arg2 = delay;
            sendHandler.sendMessage(message);
        }else {
            byte[] recvData = new byte[dataLen + 2];
            System.arraycopy(data, 0, recvData, 0, dataLen + 2);
            Message message = recvHandler.obtainMessage();
            message.what = MSG_RECV_PACKET;
            message.obj = recvData;
            recvHandler.sendMessage(message);
        }
    }

    @Override
    public void onCharacteristiChanged(UUID serviceUUID, UUID characteristicUUID, byte[] data) {
        super.onCharacteristiChanged(serviceUUID, characteristicUUID, data);
        if (serviceUUID.equals(SERVICE_UUID) && characteristicUUID.equals(SOCKET_UUID)) {
            onRecvData(data);
        }
    }

    public WLBleDataDevice() {
        super();
        sendingPacketList = new ArrayList<>();
    }

    @Override
    public boolean openBleDevice(boolean shouldStartWithScan) {
        sendThread = new HandlerThread("SendThread");
        sendThread.start();
        sendHandler = new SendHandler(sendThread.getLooper(), this);

        recvThread = new HandlerThread("RecvThread");
        recvThread.start();
        recvHandler = new RecvHandler(recvThread.getLooper(), this);

        return super.openBleDevice(shouldStartWithScan);
    }

    @Override
    public boolean closeBleDevice() {
        if (sendThread != null) {
            sendThread.quit();
            sendThread = null;
        }
        sendHandler = null;

        if (recvThread != null) {
            recvThread.quit();
            recvThread = null;
        }
        recvHandler = null;

        return super.closeBleDevice();
    }

    @Override
    public String getDeviceModel() {
        return "WLBleData Device";
    }

    @Override
    public void onScanStateChanged(int state) {

    }

    @Override
    public void onVerifying() {

    }

    @Override
    public void onReady() {
        super.onReady();
        enableCharacteristicNotification(SERVICE_UUID, SOCKET_UUID, true, NOTIFY_TYPE, new BleAction.BleActionCallback() {
            @Override
            public void onActionResult(int status, Bundle result) {
                logger.d("SOCKET_UUID onActionResult: " + status);
            }
        });

        logger.d("Negotiate mtu " + MAX_MTU);
        negotiateMTU(MAX_MTU, new BleAction.BleActionCallback() {
            @Override
            public void onActionResult(int status, Bundle result) {
                logger.d("SOCKET_UUID negotiateMTU: " + status);
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    logger.d("Negotiate Succ!");
                    setCurrentMTU(MAX_MTU);
                    dataMtu = MAX_MTU - 10;
                }else {
                    logger.d("Negotiate mtu " + MAX_MTU_SMALLER);
                    negotiateMTU(MAX_MTU_SMALLER, new BleAction.BleActionCallback() {
                        @Override
                        public void onActionResult(int status, Bundle result) {
                            logger.d("SOCKET_UUID negotiateMTU: " + status);
                            if (status == BluetoothGatt.GATT_SUCCESS) {
                                logger.d("Negotiate Succ!");
                                setCurrentMTU(MAX_MTU_SMALLER);
                                dataMtu = MAX_MTU_SMALLER - 10;
                            }else {
                                logger.d("using mtu " + MAX_MTU_SMALLEST);
                                setCurrentMTU(MAX_MTU_SMALLEST);
                                dataMtu = MAX_MTU_SMALLEST - 8;
                            }
                        }
                    });
                }
            }
        });
    }

    private void debugInfo(String info) {
        logger.d(info);
//        if (getListener() != null) {
//            Message message = new Message();
//            message.what = BleDevCommonMsg.BLE_DEV_DEBUG_MSG.getValue();
//            Bundle bundle = new Bundle();
//            bundle.putString("INFO", info);
//            message.setData(bundle);
//            getListener().onMessage(this.getBluetoothDevice(), message);
//        }
    }
}
